﻿using System;
using System.IO;

namespace Aeon.Emulator.Dos
{
    /// <summary>
    /// Represents a DOS file or device stream.
    /// </summary>
    internal sealed class DosStream
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the DosStream class.
        /// </summary>
        /// <param name="stream">System.IO.Stream instance to wrap.</param>
        /// <param name="ownerId">ID of the process which owns the stream.</param>
        public DosStream(Stream stream, int ownerId)
        {
            if(stream == null)
                throw new ArgumentNullException("stream");

            this.handle = new FileHandle(stream);
            this.OwnerId = ownerId;
        }
        private DosStream(FileHandle handle, int ownerId)
        {
            this.handle = handle;
            this.OwnerId = ownerId;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets a value indicating whether the stream is readable.
        /// </summary>
        public bool CanRead
        {
            get { return this.handle.Stream.CanRead; }
        }
        /// <summary>
        /// Gets a value indicating whether the stream is seekable.
        /// </summary>
        public bool CanSeek
        {
            get { return this.handle.Stream.CanSeek; }
        }
        /// <summary>
        /// Gets a value indicating whether the stream is writable.
        /// </summary>
        public bool CanWrite
        {
            get { return this.handle.Stream.CanWrite; }
        }
        /// <summary>
        /// Flushes the stream to its backing store.
        /// </summary>
        public void Flush()
        {
            this.handle.Stream.Flush();
        }
        /// <summary>
        /// Gets the length of the stream in bytes.
        /// </summary>
        public long Length
        {
            get { return this.handle.Stream.Length; }
        }
        /// <summary>
        /// Gets or sets the position of the stream in bytes.
        /// </summary>
        public long Position
        {
            get { return this.handle.Stream.Position; }
            set { this.handle.Stream.Position = value; }
        }
        /// <summary>
        /// Gets the wrapped System.IO.Stream.
        /// </summary>
        public Stream BaseStream
        {
            get { return this.handle.Stream; }
        }
        /// <summary>
        /// Gets the PSP which owns this file handle.
        /// </summary>
        public int OwnerId { get; private set; }
        /// <summary>
        /// Gets or sets the SFT index which refers to this file.
        /// </summary>
        public int SFTIndex
        {
            get { return this.handle.SFTIndex; }
            set { this.handle.SFTIndex = value; }
        }
        /// <summary>
        /// Gets or sets additional information about the file.
        /// </summary>
        public VirtualFileInfo FileInfo
        {
            get { return this.handle.FileInfo; }
            set { this.handle.FileInfo = value; }
        }
        /// <summary>
        /// Gets IOCTL file/device information for the handle.
        /// </summary>
        public ushort HandleInfo
        {
            get
            {
                var device = handle.Stream as IDeviceStream;
                if(device != null)
                    return (ushort)((uint)device.DeviceInfo | 0x8080u);
                else if(this.FileInfo != null)
                    return (ushort)this.FileInfo.DeviceIndex;
                else
                    return 0;
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Adds a reference to the underlying stream.
        /// </summary>
        public void AddReference()
        {
            this.handle.AddReference();
        }
        /// <summary>
        /// Clones the handle which backs this stream and assosiates it with a new process ID.
        /// </summary>
        /// <param name="newOwnerId">Process ID of cloned stream.</param>
        /// <returns>Newly-cloned stream.</returns>
        public DosStream CloneHandle(int newOwnerId)
        {
            this.handle.AddReference();
            return new DosStream(this.handle, newOwnerId);
        }
        /// <summary>
        /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
        /// </summary>
        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
        /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
        /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
        /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
        public int Read(byte[] buffer, int offset, int count)
        {
            return this.handle.Stream.Read(buffer, offset, count);
        }
        /// <summary>
        /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
        /// </summary>
        /// <returns>The unsigned byte cast to an Int32, or -1 if at the end of the stream.</returns>
        public int ReadByte()
        {
            return this.handle.Stream.ReadByte();
        }
        /// <summary>
        /// Sets the position within the current stream.
        /// </summary>
        /// <param name="offset">A byte offset relative to the origin parameter.</param>
        /// <param name="origin">A value of type System.IO.SeekOrigin indicating the reference point used to obtain the new position.</param>
        /// <returns>The new position within the current stream.</returns>
        public long Seek(long offset, SeekOrigin origin)
        {
            return this.handle.Stream.Seek(offset, origin);
        }
        /// <summary>
        /// Sets the length of the current stream.
        /// </summary>
        /// <param name="value">The desired length of the current stream in bytes.</param>
        public void SetLength(long value)
        {
            this.handle.Stream.SetLength(value);
        }
        /// <summary>
        /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
        /// </summary>
        /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
        /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
        /// <param name="count">The number of bytes to be written to the current stream.</param>
        public void Write(byte[] buffer, int offset, int count)
        {
            this.handle.Stream.Write(buffer, offset, count);
        }
        /// <summary>
        /// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
        /// </summary>
        /// <param name="value">The byte to write to the stream.</param>
        public void WriteByte(byte value)
        {
            this.handle.Stream.WriteByte(value);
        }
        /// <summary>
        /// Decrements the underlying stream's reference count.
        /// </summary>
        public void Close()
        {
            this.handle.Close();
        }
        #endregion

        #region Private Fields
        /// <summary>
        /// The DOS file handle.
        /// </summary>
        private FileHandle handle;
        #endregion
    }
}
